package top.rainbowecho.gateway.security.authentication.mail;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.bind.ServletRequestBindingException;
import top.rainbowecho.common.util.ExceptionMessageContent;
import top.rainbowecho.common.util.RequestKey;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

import static org.apache.commons.lang3.StringUtils.isEmpty;

/**
 * 此过滤器通过表单形式提交，content-type为application/x-www-form-urlencoded
 *
 * @author rainbow
 * @since 2019/12/15 10:15
 */
@Slf4j
public class MailAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    public static final String SECURITY_FORM_MAIL_KEY = "mail";

    private String mailParameter = SECURITY_FORM_MAIL_KEY;
    private boolean postOnly = true;
    // ~ Constructors
    // ===================================================================================================

    public MailAuthenticationFilter(String processUrl) {
        super(new AntPathRequestMatcher(processUrl, "POST"));
    }

    // ~ Methods
    // ========================================================================================================

    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException, ServletRequestBindingException, IOException {
        if (postOnly && !HttpMethod.POST.matches(request.getMethod())) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }

        String mail = obtainMail(request);
        log.debug("mail param: " + mail);
        long timestamp = Long.parseLong(request.getParameter(RequestKey.TIMESTAMP));
        log.debug("timestamp param: " + timestamp);
        String code = request.getParameter(RequestKey.CODE);
        log.debug("code param: " + code);

        if (isEmpty(code)) {
            throw new MailAuthenticationException(ExceptionMessageContent.EMPTY_CODE);
        }

        if (isEmpty(mail)) {
            throw new MailAuthenticationException(ExceptionMessageContent.MISSING_ARG);
        }

        MailAuthenticationToken authRequest = new MailAuthenticationToken(mail, timestamp, code);

        // Allow subclasses to set the "details" property
        this.setDetails(request, authRequest);

        return this.getAuthenticationManager().authenticate(authRequest);
    }

    /**
     * Enables subclasses to override the composition of the username, such as by
     * including additional values and a separator.
     *
     * @param request so that request attributes can be retrieved
     *
     * @return the username that will be presented in the <code>Authentication</code>
     * request token to the <code>AuthenticationManager</code>
     */
    protected String obtainMail(HttpServletRequest request) {
        return request.getParameter(mailParameter);
    }

    /**
     * Provided so that subclasses may configure what is put into the authentication
     * request's details property.
     *
     * @param request that an authentication request is being created for
     * @param authRequest the authentication request object that should have its details
     * set
     */
    protected void setDetails(HttpServletRequest request,
                              MailAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

    /**
     * Sets the parameter name which will be used to obtain the username from the login
     * request.
     *
     * @param mailParameter the parameter name. Defaults to "username".
     */
    public void setMailParameter(String mailParameter) {
        Assert.hasText(mailParameter, "Username parameter must not be empty or null");
        this.mailParameter = mailParameter;
    }

    /**
     * Defines whether only HTTP POST requests will be allowed by this filter. If set to
     * true, and an authentication request is received which is not a POST request, an
     * exception will be raised immediately and authentication will not be attempted. The
     * <tt>unsuccessfulAuthentication()</tt> method will be called as if handling a failed
     * authentication.
     * <p>
     * Defaults to <tt>true</tt> but may be overridden by subclasses.
     */
    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;
    }

    public final String getMailParameter() {
        return mailParameter;
    }
}
